package server;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import server.mapper.Context;
import server.mapper.Host;
import server.mapper.Mapper;
import server.mapper.Wrapper;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.*;

/**
 * Minicat v4.0主类
 * @autor yzh
 * @date 2020/2/25 22:27
 */
public class Bootstrap {

    /**
     * 定义socket监听的端口号 : 根据server.xml配置文件赋值
     */
    private Integer port;

    /**
     * 映射实体对象
     */
    private Mapper mapper = new Mapper();

    /**
     * Minicat的启动方法 : 进行一些必要的初始化展开操作
     */
    public void start() throws Exception {
        //加载并解析server.xml
        loadSever();
        //定义一个线程池
        int corePoolSize = 10;
        int maximumPoolSize = 50;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler
        );
        //监听指定端口
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port：" + port);
        //对请求进行多线程处理
        while(true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket,mapper);
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    /**
     * 加载并解析server.xml的方法
     */
    private void loadSever() {
        try {
            //1.加载配置文件
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
            //2.解析配置文件
            //2.1 进行解析
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> serverElementList = rootElement.selectNodes("//Server");
            if(serverElementList == null || serverElementList.size() == 0) return;
            Element serviceElement = serverElementList.get(0).element("Service");
            Element connectorElement = serviceElement.element("Connector");
            //2.2 读取端口号并赋值
            port = Integer.parseInt(connectorElement.attribute("port").getText());
            //3.封装主机实体对象
            Element engineElement = connectorElement.element("Engine");
            List<Element> hostElementList = engineElement.elements("Host");
            if(hostElementList == null && hostElementList.size() > 0) return;
            for (int i = 0; i < hostElementList.size(); i++) {
                Host host = new Host(hostElementList.get(i).attribute("name").getText(),hostElementList.get(i).attribute("appBase").getText());
                //4.封装上下文内容实体对象
                File appBaseFile = new File(hostElementList.get(i).attribute("appBase").getText());
                if(!appBaseFile.exists() || !appBaseFile.isDirectory()) continue;
                File[] files = appBaseFile.listFiles();
                for (int j = 0; j < files.length; j++) {
                    if(files[i].isDirectory()) {
                        Context context = new Context(files[j].getName(), files[j].getAbsolutePath());
                        loadServlet(files[j].getAbsolutePath(),context);
                        //5.封装包装实体对象
                        host.getContextList().add(context);
                    }
                }
                mapper.getHostList().add(host);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 加载并解析web.xml以及初始化Servlet的方法
     * @param path 应用部署目录,如demo1,demo2
     * @param context 应用的上下文内容实例对象
     */
    private void loadServlet(String path, Context context) {
        try {
            InputStream resourceAsStream = new FileInputStream(path + "/" + "web.xml");
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element =  selectNodes.get(i);
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();
                //根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                //获取servlet对象
                MyClassLoader myClassLoader = new MyClassLoader();
                Class<?> clazz = myClassLoader.findClass(path + File.separator, servletClass);
                Object o = clazz.newInstance();
                Wrapper wrapper = new Wrapper(urlPattern, (HttpServlet)o);
                context.getWrapperList().add(wrapper);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            //启动Minicat
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
